﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;
using DeepEarth.Toolkit.Geometry.Data;
using DeepEarth.Core;
using System.Collections.ObjectModel;
using DeepEarth.Toolkit.Geometry;
using System.Windows.Threading;

namespace ExampleApplication.Examples
{
    public partial class ShowDayNight : Page
    {
        public ShowDayNight()
        {
            InitializeComponent();
        }

        // Executes when the user navigates to this page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            InitializeDayNight();

            this.map.ViewChangeOnFrame += new EventHandler(map_ViewChangeOnFrame);
        }

        void map_ViewChangeOnFrame(object sender, EventArgs e)
        {
            timer.Interval = TimeSpan.FromSeconds(2 / map.ZoomLevel);
        }

        BitmapLayer bml;
        DispatcherTimer timer;

        void InitializeDayNight()
        {
            bml = new BitmapLayer();
            this.map.BaseChildren.Add(bml);

            StartTimer();
        }

        void StartTimer()
        {
            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(1);
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            var geo = ComputeDayNightGeometry();

            geo.Fill = new SolidColorBrush(Color.FromArgb(100, 0, 0, 0));
            bml.Clear();
            bml.Add(geo);
            bml.Refresh();
        }

        // http://www.geoastro.de
        double JulDay(DateTime now)
        {
            double ut = now.TimeOfDay.TotalHours;
            var currentDate = now.Date;

            int year = currentDate.Year;
            int month = currentDate.Month;
            int date = currentDate.Day;

            if (year < 1900) year = year + 1900;
            if (month <= 2) { month = month + 12; year = year - 1; }
            const int B = -13;
            return Math.Floor(365.25 * (year + 4716)) + Math.Floor(30.6001 * (month + 1)) + date + B - 1524.5 + ut / 24.0;
        }

        double Declination(DateTime now)
        {
            const double k = Math.PI / 180.0;
            var jd = JulDay(now);
            var T = (jd - 2451545.0) / 36525.0;
            var l0 = 280.46645 + (36000.76983 + 0.0003032 * T) * T;
            var m = 357.52910 + (35999.05030 - (0.0001559 * T + 0.00000048 * T) * T) * T;
            m = k * m;
            var C = (1.914600 - 0.004817 * T - 0.000014 * T * T) * Math.Sin(m) + (0.019993 - 0.000101 * T) * Math.Sin(2 * m) + 0.000290 * Math.Sin(3 * m);
            var theta = l0 + C;
            var omega = 125.04 - 1934.136 * T;
            var lambda = theta - 0.00569 - 0.00478 * Math.Sin(k * omega);
            var eps0 = 23.0 + 26.0 / 60.0 + 21.448 / 3600.0 - (46.8150 * T + 0.00059 * T * T - 0.001813 * T * T * T) / 3600;
            var eps = eps0 + 0.00256 * Math.Cos(k * omega);
            var declin = Math.Sin(k * eps) * Math.Sin(k * lambda);
            declin = Math.Asin(declin) / k;
            var ra = Math.Atan2(Math.Cos(k * eps) * Math.Sin(k * lambda), Math.Cos(k * lambda)) / k;
            if (ra < 0) ra = ra + 360;
            return declin;
        }

        public const double BingMapsMaxLatitiude = 85.051128;

        PolygonGeometry ComputeDayNightGeometry()
        {
            var now = DateTime.UtcNow;
            var localTime = now.TimeOfDay;

            var diffNoon = localTime - TimeSpan.FromHours(12);

            var fraction = diffNoon.TotalMinutes / 60.0;


            double K = Math.PI / 180.0;
            double dec = Declination(now);

            // we can't handle vertical lines! When the declination is zero
            // the day night terminator is vertical. In that case, there
            // is no area above or below the line to shade. Instead of 
            // handling left/right of the line instead, just don't allow vertical
            // lines.
            if (Math.Abs(dec) < 0.51)
            {
                dec = dec < 0 ? -0.51 : 0.51;
            }

            // To calculate tau, +12 hours = 180, -12 hours is -180
            double tau = fraction * 15; // Hour Angle of the Sun

            var locations = new List<Location>();

            // http://www.geoastro.de/map/map03.txt
            for (int i = -180; i <= 180; i++)
            {
                double longitude = i + tau;
                double tanLat = -Math.Cos(longitude * K) / Math.Tan(dec * K);
                double latitude = Math.Atan(tanLat) / K;

                locations.Add(new Location(latitude, i));
            }

            // if the declination angle is negative, night is above the line. Otherwise, night is below
            var yLat = dec < 0 ? BingMapsMaxLatitiude : -BingMapsMaxLatitiude;

            locations.Add(new Location(yLat, 180));
            locations.Add(new Location(yLat, -180));
            locations.Add(locations[0].Clone());

            PolygonGeometry pg = new PolygonGeometry();
            pg.Locations = new ObservableCollection<Location>(locations);

            return pg;
        }

    }
}
